package com.lwq.sensitive.aspect;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.lwq.sensitive.annotation.SensitiveEnable;
import com.lwq.sensitive.annotation.SensitiveFiled;
import com.lwq.sensitive.constant.Constants;
import com.lwq.sensitive.enums.SensitiveStrategyEnum;
import com.lwq.sensitive.support.PermissionSupport;
import com.lwq.sensitive.utils.SensitiveUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

@Slf4j
@Aspect
public class SensitiveEnableAspect {

    @Pointcut("@annotation(com.lwq.sensitive.annotation.SensitiveEnable)")
    public void sensitiveEnable(){}

    @Around("sensitiveEnable()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{

        long startTime = System.currentTimeMillis();

        Object target = joinPoint.getTarget();
        if (ObjectUtil.isNull(target)){
            return joinPoint.proceed();
        }
        Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
        Method method = target.getClass().getMethod(joinPoint.getSignature().getName(), parameterTypes);
        SensitiveEnable annotation = method.getAnnotation(SensitiveEnable.class);
        if (ObjectUtil.isNull(annotation)){
            return joinPoint.proceed();
        }
        if (!annotation.enableAround()){
            return joinPoint.proceed();
        }
        PermissionSupport permissionSupport = null;
        if (annotation.enablePermissionSupport()){
            String beanName = annotation.permissionSupportBeanName();
            try{
                permissionSupport = SpringUtil.getBean(beanName);
            }catch (Exception e){
                log.error("未获取到对应bean信息",e);
            }
            if (null == permissionSupport){

                try{
                    // 如果根据名称未获取到，则再根据类获取一遍
                    permissionSupport = SpringUtil.getBean(PermissionSupport.class);
                }catch (Exception e){
                    log.error("未获取到对应bean信息",e);
                }
                if (null == permissionSupport){
                    // 说明没有实现 PermissionSupport 这个接口，没有脱敏权限
                    return joinPoint.proceed();
                }
            }
        }


        Object[] args = joinPoint.getArgs();
        if (null == args && args.length <= 0 ){
            return joinPoint.proceed();
        }
        for (Object argItem : args){
            if (argItem instanceof Collection) {
                Collection collection = (Collection) argItem;
                if (CollectionUtil.isNotEmpty(collection)){
                    for (Object entity : collection) {
                        try {
                            decryptSensitive(entity, permissionSupport, annotation, argItem);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }

            } else if(argItem instanceof Map) {
                Map<String, Object> map = (Map) argItem;
                if (CollectionUtil.isNotEmpty(map)){
                    for (Map.Entry<String, Object> myMap : map.entrySet()){
                        try {
                            Object value = myMap.getValue();
                            decryptSensitive(value, permissionSupport, annotation, argItem);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }else {
                decryptSensitive(argItem, permissionSupport, annotation, argItem);
            }
        }

        log.info("around exeTime ={}" , System.currentTimeMillis() - startTime);
        return joinPoint.proceed();
    }



    @AfterReturning(value = "sensitiveEnable()", returning = "result")
    public Object doAfterReturning(JoinPoint joinPoint, Object result){
        long startTime = System.currentTimeMillis();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        SensitiveEnable annotation = method.getAnnotation(SensitiveEnable.class);
        if (ObjectUtil.isNull(annotation)){
            return result;
        }
        if (!annotation.enableAfterReturning()){
            return result;
        }

        PermissionSupport permissionSupport = null;
        if (annotation.enablePermissionSupport()){
            String beanName = annotation.permissionSupportBeanName();
            try{
                permissionSupport = SpringUtil.getBean(beanName);
            }catch (Exception e){
                log.error("未获取到对应bean信息",e);
            }
            if (null == permissionSupport){
                try{
                    // 如果根据名称未获取到，则再根据类获取一遍
                    permissionSupport = SpringUtil.getBean(PermissionSupport.class);
                }catch (Exception e){
                    log.error("未获取到对应bean信息",e);
                }
                if (null == permissionSupport){
                    // 说明没有实现 PermissionSupport 这个接口，没有脱敏权限
                    return result;
                }

            }
        }

        if (result instanceof Collection) {
            Collection collection = (Collection) result;
            if (CollectionUtil.isNotEmpty(collection)){
                for (Object entity : collection) {
                    try {
                        encryptSensitive(entity, permissionSupport, annotation, result);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }

        } else if(result instanceof Map) {
            Map<String, Object> map = (Map) result;
            if (CollectionUtil.isNotEmpty(map)){
                for (Map.Entry<String, Object> myMap : map.entrySet()){
                    try {
                        Object value = myMap.getValue();
                        encryptSensitive(value, permissionSupport, annotation, result);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }else {
            try {
                encryptSensitive(result, permissionSupport, annotation, result);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        log.info("after returning exeTime ={}" , System.currentTimeMillis() - startTime);
        return result;
    }

    /**
     * 脱敏处理
     *
     * @param obj
     * @param permissionSupport
     * @param sensitiveEnableAnnotation
     * @param objData
     */
    private void encryptSensitive(Object obj, PermissionSupport permissionSupport, SensitiveEnable sensitiveEnableAnnotation, Object objData) throws IllegalAccessException {
        if (ObjectUtil.isNull(obj)) {
            return;
        }
        if (isBasicTypeObj(obj)){
            return;
        }
        if (obj instanceof List){
            List objList = (List) obj;
            if (CollectionUtil.isNotEmpty(objList)){
                for (Object o : objList){
                    encryptSensitive(o, permissionSupport, sensitiveEnableAnnotation, objData);
                }
            }
        }else if (obj instanceof Map){
            Map objMap = (Map) obj;
            if (CollectionUtil.isNotEmpty(objMap)){
                objMap.forEach((key,value)->{
                    try{
                        encryptSensitive(value, permissionSupport, sensitiveEnableAnnotation, objData);
                    }catch (Exception e){
                        log.error("执行出错",e);
                    }
                });
            }

        }else{
            List<Field> fields = new ArrayList<>();
            geteAllFileds(obj, fields);
            if (CollectionUtil.isEmpty(fields)){
                return;
            }
            for (Field field : fields) {
                field.setAccessible(true);
                Class<?> type = field.getType();
                if (ObjectUtil.isNotNull(type)){

                    String name = type.getName();
                    if (!isBasicType(name)){
                        if (type.equals(List.class)){
                            List list = (List) field.get(obj);
                            if (CollectionUtil.isNotEmpty(list)){
                                for (Object listObj : list){
                                    encryptSensitive(listObj, permissionSupport, sensitiveEnableAnnotation, objData);
                                }
                            }
                        }else if (type.equals(Map.class)){
                            Map<String,Object> map = (Map) field.get(obj);
                            if (CollectionUtil.isNotEmpty(map)){
                                for( Map.Entry<String,Object> myMap : map.entrySet()){
                                    Object value = myMap.getValue();
                                    encryptSensitive(value, permissionSupport, sensitiveEnableAnnotation, objData);
                                }
                            }
                        }else{
                            try{
                                Object value = field.get(obj);
                                encryptSensitive(value, permissionSupport, sensitiveEnableAnnotation, objData);
                            }catch (Exception e){
                                log.error("error",e);
                            }
                        }
                    }

                }
                SensitiveFiled annotation = field.getAnnotation(SensitiveFiled.class);
                if (ObjectUtil.isNull(annotation)){
                    continue;
                }
                SensitiveStrategyEnum strategy = annotation.strategy();
                if (ObjectUtil.isNull(strategy)){
                    continue;
                }

                if (sensitiveEnableAnnotation.enablePermissionSupport()){
                    Predicate<Object> predicate = permissionSupport.doSensitive(objData);
                    if (predicate.test(obj)){
                        doEncrySensitive(obj, field, strategy);
                    }
                }else{
                    doEncrySensitive(obj, field, strategy);
                }

            }
        }


    }

    /**
     * 执行脱敏动作
     *
     * @param obj
     * @param field
     * @param strategy
     * @throws IllegalAccessException
     */
    private void doEncrySensitive(Object obj, Field field, SensitiveStrategyEnum strategy) throws IllegalAccessException {
        Object o = null;
        try{
            o = field.get(obj);
        }catch (Exception e){
            log.error("handleSensitive error", e);
        }
        String sensitive = null;
        switch (strategy){
            case USER_NAME:
                sensitive = SensitiveUtils.userName(ObjectUtil.isNotNull(o) ? o.toString() : null);
                break;
            case ID_CARD:
                sensitive = SensitiveUtils.idCard(ObjectUtil.isNotNull(o) ? o.toString() : null);
                break;
            case PHONE:
                sensitive = SensitiveUtils.phone(ObjectUtil.isNotNull(o) ? o.toString() : null);
                break;
            case EMAIL:
                sensitive = SensitiveUtils.email(ObjectUtil.isNotNull(o) ? o.toString() : null);
                break;
            case BANK_CARD:
                sensitive = SensitiveUtils.bankCard(ObjectUtil.isNotNull(o) ? o.toString() : null);
                break;
            case EN_CREPT:
                sensitive = SensitiveUtils.encrypt(o);
                break;
        }
        field.set(obj, sensitive);
    }


    /**
     * 解密
     *
     * @param obj
     */
    private void decryptSensitive(Object obj, PermissionSupport permissionSupport, SensitiveEnable sensitiveEnableAnnotation, Object objData) throws IllegalAccessException {
        if (ObjectUtil.isNull(obj)) {
            return;
        }
        if (isBasicTypeObj(obj)){
            return;
        }

        if (obj instanceof List){
            List objList = (List) obj;
            if (CollectionUtil.isNotEmpty(objList)){
                for (Object o : objList){
                    decryptSensitive(o, permissionSupport, sensitiveEnableAnnotation, objData);
                }
            }
        }else if (obj instanceof Map){
            Map objMap = (Map) obj;
            if (CollectionUtil.isNotEmpty(objMap)){
                objMap.forEach((key,value)->{
                    try{
                        decryptSensitive(value, permissionSupport, sensitiveEnableAnnotation, objData);
                    }catch (Exception e){
                        log.error("执行出错",e);
                    }
                });
            }

        }else{
            List<Field> fields = new ArrayList<>();
            geteAllFileds(obj, fields);
            if (CollectionUtil.isEmpty(fields)){
                return;
            }

            for (Field field : fields) {
                field.setAccessible(true);
                Class<?> type = field.getType();
                if (ObjectUtil.isNotNull(type)){
                    String name = type.getName();
                    if (!isBasicType(name)){
                        if (type.equals(List.class)){
                            List list = (List) field.get(obj);
                            if (CollectionUtil.isNotEmpty(list)){
                                for (Object listObj : list){
                                    decryptSensitive(listObj, permissionSupport, sensitiveEnableAnnotation, objData);
                                }
                            }
                        }else if (type.equals(Map.class)){
                            Map<String,Object> map = (Map) field.get(obj);
                            if (CollectionUtil.isNotEmpty(map)){
                                for( Map.Entry<String,Object> myMap : map.entrySet()){
                                    Object value = myMap.getValue();
                                    decryptSensitive(value, permissionSupport, sensitiveEnableAnnotation, objData);
                                }
                            }
                        }else{
                            try{
                                Object value = field.get(obj);
                                decryptSensitive(value, permissionSupport, sensitiveEnableAnnotation, objData);
                            }catch (Exception e){
                                log.error("error",e);
                            }
                        }
                    }
                }
                SensitiveFiled annotation = field.getAnnotation(SensitiveFiled.class);
                if (ObjectUtil.isNull(annotation)){
                    continue;
                }
                SensitiveStrategyEnum strategy = annotation.strategy();
                if (ObjectUtil.isNull(strategy)){
                    continue;
                }

                if (sensitiveEnableAnnotation.enablePermissionSupport()){
                    Predicate<Object> predicate = permissionSupport.doSensitive(objData);
                    if (predicate.test(obj)){
                        doDecrySensitive(obj, field, strategy);
                    }
                }else{
                    doDecrySensitive(obj, field, strategy);
                }

            }
        }

    }

    /**
     * 执行解密
     *
     * @param obj
     * @param field
     * @param strategy
     * @throws IllegalAccessException
     */
    private void doDecrySensitive(Object obj, Field field, SensitiveStrategyEnum strategy) throws IllegalAccessException {
        // 只处理 加密类型，对其解密
        if (SensitiveStrategyEnum.EN_CREPT.name().equals(strategy.name())){
            String decrypt = null;
            try{
                Object o = field.get(obj);
                if (ObjectUtil.isNotNull(o)){
                    String encryptStr   = o.toString();
                    if (encryptStr.contains(Constants.PRE_FIX)){
                        // 如果包括加密的前缀，说明是加密的字符串，需要将空格替换成 +
                        encryptStr = encryptStr.replace(" ", "+");
                    }
                    decrypt = SensitiveUtils.decrypt(encryptStr);
                }
            }catch (Exception e){
                log.error("handleSensitive error", e);
            }
            field.set(obj, decrypt);
        }
    }

    /**
     * 获取全部字段（包括父类中的字段）
     *
     * @param obj
     * @param fieldList 获取到的全部的字段
     */
    private static void geteAllFileds(Object obj,   List<Field> fieldList) {
        Class clazz = obj.getClass();
        if (obj instanceof List){
           List objList = (List) obj;
           if (CollectionUtil.isNotEmpty(objList)){
               for (Object o : objList ){
                   geteAllFileds(o, fieldList);
               }
           }
        }else if (obj instanceof Map){
            Map objMap = (Map) obj;
            if (CollectionUtil.isNotEmpty(objMap)){
                objMap.forEach((key,value) ->{
                    geteAllFileds(value, fieldList);
                });
            }
        }else{
            ClassLoader classLoader = clazz.getClassLoader();
            while (clazz != null && ObjectUtil.isNotNull(classLoader) ) {
                fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
                clazz = clazz.getSuperclass();
            }
        }
    }


    /**
     * 是否是基本类型
     * @param typeName
     * @return
     */
    private boolean isBasicType(String typeName) {
        if (StrUtil.equalsIgnoreCase(typeName, "byte") || StrUtil.equalsIgnoreCase(typeName, "java.lang.Byte")
                ||StrUtil.equalsIgnoreCase(typeName, "char") || StrUtil.equalsIgnoreCase(typeName, "java.lang.Character")
                || StrUtil.equalsIgnoreCase(typeName, "short") || StrUtil.equalsIgnoreCase(typeName, "java.lang.Short")
                || StrUtil.equalsIgnoreCase(typeName, "int") || StrUtil.equalsIgnoreCase(typeName, "java.lang.Integer")
                || StrUtil.equalsIgnoreCase(typeName, "double") || StrUtil.equalsIgnoreCase(typeName, "java.lang.Double")
                || StrUtil.equalsIgnoreCase(typeName, "float") || StrUtil.equalsIgnoreCase(typeName, "java.lang.Float")
                || StrUtil.equalsIgnoreCase(typeName, "long") || StrUtil.equalsIgnoreCase(typeName, "java.lang.Long")
                || StrUtil.equalsIgnoreCase(typeName, "boolean") || StrUtil.equalsIgnoreCase(typeName, "java.lang.Boolean")
                || StrUtil.equalsIgnoreCase(typeName, "java.lang.String") ){
            return true;
        }

        return false;
    }

    /**
     * 是否是基础类型
     *
     * @param o
     * @return
     */
    private static boolean isBasicTypeObj(Object o) {
        return o instanceof Byte || o instanceof Short || o instanceof Integer || o instanceof Double
                || o instanceof Float || o instanceof Long || o instanceof Character || o instanceof Boolean
                || o instanceof String;
    }
}
